package gui.batches;

import gui.batches.command.CommandHistory;
import gui.batches.command.TransferItemsCommand;
import gui.common.Controller;
import gui.common.IView;
import gui.inventory.ProductContainerData;
import gui.item.ItemData;
import gui.product.ProductData;
import hit.core.Barcode;
import hit.core.HomeInventoryTracker;
import hit.core.Item;
import hit.core.ItemFacade;
import hit.core.Product;
import hit.core.ProductContainer;
import hit.core.ProductFacade;
import hit.core.StorageUnit;
import hit.dao.DataAccessObject;
import hit.dao.FactoryReference;
import hit.util.DataConverter;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Action;
import javax.swing.Timer;

/**
 * Controller class for the transfer item batch view.
 */
public class TransferItemBatchController extends Controller implements
		ITransferItemBatchController, Action{
	
	private static final int BARCODE_TIMEOUT = 750;
	ItemFacade itemFacade;
	ProductFacade productFacade;
	CommandHistory commandHistory;
	
	ProductContainerData target;
	int count;
	Barcode barcode;
	private List<Item> transferredItems;
	
	boolean useTimer;
	Timer timer;
	
	Map<ProductData, List<ItemData>> dataMapping;
	/**
	 * Constructor.
	 * 
	 * @param view Reference to the transfer item batch view.
	 * @param target Reference to the storage unit to which items are being transferred.
	 */
	public TransferItemBatchController(IView view, ProductContainerData target) {
		super(view);
		itemFacade = HomeInventoryTracker.Instance().GetItemFacade();
		productFacade = HomeInventoryTracker.Instance().GetProductFacade();
		commandHistory = new CommandHistory();
		
		count = 1;
		barcode = null;
		this.target =  target;
		
		useTimer = true;
		timer = null;
		
		transferredItems = new ArrayList<Item>();
		dataMapping = new HashMap<ProductData, List<ItemData>>();
		construct();
	}
	
	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected ITransferItemBatchView getView() {
		return (ITransferItemBatchView)super.getView();
	}

	/**
	 * Loads data into the controller's view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() {
		getView().setUseScanner(useTimer);
	}

	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() {
		getView().enableItemAction(true);
		
		//check if we should enable the transfer item button
		boolean shouldEnable = true;
		if (getView().getBarcode().equals("") || getView().getUseScanner())
			shouldEnable = false;
		getView().enableItemAction(shouldEnable);
		
		//check if undo should be enabled
		if (commandHistory.CanUndo())
			getView().enableUndo(true);
		else
			getView().enableUndo(false);
		
		//check if redo should be enabled
		if (commandHistory.CanRedo()) 
			getView().enableRedo(true);
		else
			getView().enableRedo(false);
	}

	/**
	 * This method is called when the "Item Barcode" field in the
	 * transfer item batch view is changed by the user.
	 */
	@Override
	public void barcodeChanged() {

		//barcode.SetValue(getView().getBarcode());
		
		if (timer != null) {
			timer.stop();
			timer = null;
		}
		
		try {
			String newBarcodeValue = getView().getBarcode();  
			if (!"".equals(newBarcodeValue))
			{
				if (useTimer)
				{
					timer = new Timer(BARCODE_TIMEOUT, this);
					timer.start();
				}
				
				barcode = new Barcode(getView().getBarcode());
			}
			else
				barcode = null;
		}
		catch(Exception e) {
			barcode = null;
		}
		finally {
			enableComponents();
		}
	}
	
	/**
	 * This method is called when the "Use Barcode Scanner" setting in the
	 * transfer item batch view is changed by the user.
	 */
	@Override
	public void useScannerChanged() {
		useTimer = getView().getUseScanner();
		
		if (useTimer) {
			barcode = null;
			getView().setBarcode("");
		}
		else {
			if (timer != null)
			{
				timer.stop();
				timer = null;
			}
		}
		
		enableComponents();
	}
	
	/**
	 * This method is called when the selected product changes
	 * in the transfer item batch view.
	 */
	@Override
	public void selectedProductChanged() {
		DrawItems();
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		
		if (Barcode.IsValid(barcode))
			transferItem();
		else{
			getView().displayErrorMessage("Invalid Barcode.");
		}
		
		if (timer.isRunning())
			timer.stop();
		
		timer = null;
		enableComponents();
	}
	
	/**
	 * This method is called when the user clicks the "Transfer Item" button
	 * in the transfer item batch view.
	 */
	@Override
	public void transferItem() {
		Item item = itemFacade.GetItemByBarcode(barcode.GetValue());
		
		if (!Barcode.IsValid(barcode) || item == null){
			getView().displayErrorMessage("The specified item doesn't exist.");
			getView().setBarcode("");
			return;
		}
		
		//if the item's already been transferred, return
		if (transferredItems.contains(item)) {
			getView().displayErrorMessage("The specified item was already transferred.");
			getView().setBarcode("");
			return;
		}
		
		//transfer the item
		StorageUnit storage = (StorageUnit)target.getTag();
		List<Item> itemsToTransfer = new ArrayList<Item>();
		itemsToTransfer.add(item);
		ProductContainer destination = 
				HomeInventoryTracker.Instance().GetProductIndex().
				FindProductContainer(storage, item.GetProduct());
		commandHistory.Execute(new TransferItemsCommand(itemsToTransfer, 
													item.GetContainer(), destination));
		transferredItems.add(item);
		
		Product product = item.GetProduct();
		UpdateView(item, product, true);

		getView().setBarcode("");
		getView().giveBarcodeFocus();
		enableComponents();
	}

	private void UpdateView(Item item, Product product, boolean transferred) {
		ProductData pdata = GetDataMappingByProductBarcode(product.GetBarcode().GetValue());
		if (pdata == null){
			pdata = DataConverter.TranslateProduct(product);
		}
		List<ItemData> idatas = dataMapping.get(pdata);
		if (idatas == null){
			idatas = new ArrayList<ItemData>();
		}
		
		if (transferred)
			idatas.add(DataConverter.TranslateItem(item));
		else
			idatas.remove(DataConverter.TranslateItem(item));
		
		//if there are no items left, remove the product
		if (idatas.size() == 0) {
			dataMapping.remove(pdata);
			getView().selectProduct(null);
		}
		else {
			pdata.setCount(Integer.toString(idatas.size()));
			dataMapping.put(pdata, idatas);
		}
		
		DrawProducts();
	}
	
	private ProductData GetDataMappingByProductBarcode(String barcode){
		for (ProductData pdata : dataMapping.keySet()){
			if (pdata.getBarcode().equals(barcode)){
				return pdata;
			}
		}
		return null;
	}
	
	private void DrawProducts() {
		ProductData prevSelected = getView().getSelectedProduct();
		
		Set<ProductData> products = dataMapping.keySet();
		
		ProductData[] selectedProductArray = new ProductData[products.size()];
		selectedProductArray = products.toArray(selectedProductArray);
		getView().setProducts(selectedProductArray);
		
		if (prevSelected != null) {
			getView().selectProduct(prevSelected);
			DrawItems();
		}
		else
		{
			getView().setItems(new ItemData[0]);
		}
	}
	
	private void DrawItems() {
		
		ProductData selectedProduct = getView().getSelectedProduct();
		
		List<ItemData> selectedItems = dataMapping.get(selectedProduct);
		if (selectedItems == null) {
			getView().setItems(new ItemData[0]);
			return;
		}
		
		ItemData[] selectedItemArray = new ItemData[selectedItems.size()];
		selectedItemArray = selectedItems.toArray(selectedItemArray);
		getView().setItems(selectedItemArray);
	}
	
	/**
	 * This method is called when the user clicks the "Redo" button
	 * in the transfer item batch view.
	 */
	@Override
	public void redo() {
		List<Item> items = commandHistory.Redo();
		UpdateView(items.get(0), items.get(0).GetProduct(), true);
		transferredItems.addAll(items);
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in the transfer item batch view.
	 */
	@Override
	public void undo() {
		List<Item> items = commandHistory.Undo();
		UpdateView(items.get(0), items.get(0).GetProduct(), false);
		transferredItems.removeAll(items);
		
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Done" button
	 * in the transfer item batch view.
	 */
	@Override
	public void done() {
		
		ArrayList<Product> productsUpdated = new ArrayList<Product>();
		
		DataAccessObject dao = FactoryReference.GetFactory().CreateDataAccessObject();
		dao.StartTransaction();
		
		for (Item i : transferredItems)
		{
			dao.GetItemDAO().Update(i);
			dao.EndTransaction(true);
			
			if (!productsUpdated.contains(i.GetProduct()))
			{
				productsUpdated.add(i.GetProduct());
				dao.EndTransaction(true);
			}
		}
		
		for (Product p : productsUpdated)
		{
			dao.GetProductDAO().Update(p);
		}
		
		dao.CloseConnection();
		
		getView().close();
	}
	
	@Override
	public void addPropertyChangeListener(PropertyChangeListener arg0)
											throws UnsupportedOperationException {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void removePropertyChangeListener(PropertyChangeListener arg0) 
											throws UnsupportedOperationException {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}
	
	@Override
	public Object getValue(String arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isEnabled() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void putValue(String arg0, Object arg1) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void setEnabled(boolean arg0) {
		// TODO Auto-generated method stub
		
	}
}

